#include "PreCompile.hpp"
#include "Utils.hpp"
#include "Exception.hpp"

namespace BuildNG
{
  Glib::ustring int_to_str(gint32 v)
  {
    if(v == 0)
      return "0";
    else if(v == -2147483647 - 1)
    {
      // the abs value of this integer doesn't fit in an 32 bit int, so
      // we just treat it as a special case.
      return "-2147483648";
    }
    else
    {
      Glib::ustring ret;

      bool negative = false;
      if(v < 0)
      {
        negative = true;
        v = -v;
      }

      while(v)
      {
        ret = (char)((v % 10) + '0') + ret;
        v /= 10;
      }

      if(negative)
        return '-' + ret;
      else
        return ret;
    }
  }

  TryParseIntRes try_parse_int(const Glib::ustring &str,int &res)
  {
    const gchar *begin = str.c_str();
    const gchar *end = begin + str.bytes();
    return try_parse_int(begin,end,res);
  }

  TryParseIntRes try_parse_int(const gchar *str,int &res)
  {
    const gchar *end = str;
    while(*end)
      end++;
    return try_parse_int(str,end,res);
  }

  TryParseIntRes try_parse_int(const gchar *begin,const gchar *end,int &res)
  {
    const gchar *c = begin;

    res = 0;

    // skip initial whitespace.
    while(c != end && isspace(*c) )
      c++;

    bool negative = false;

    if(c != end && *c == '-')
    {
      negative = true;
      c++;
    }

    if(c == end || (*c < '0' || *c > '9'))
      return TryParseIntRes_ParseError;

    // parse it, and compute the negative value. We compute the
    // negative value, because an integer has a bigger negative range.
    while(c != end && (*c >= '0' && *c <= '9'))
    {
      if(res < (INT_MIN/10))
        return TryParseIntRes_OutOfRange;

      res *= 10;
      res -= *c - '0';

      c++;
    }

    while(isspace(*c))
      c++;

    if(c != end)
      return TryParseIntRes_ParseError;

    if(!negative)
    {
      if(res == INT_MIN)
        return TryParseIntRes_OutOfRange;
      // it fits in a positive int, so negate it to get the positive value.
      // (it's currently negative).
      res = -res;
    }

    return TryParseIntRes_Success;
  }

  gint32 parse_int(const Glib::ustring &str)
  {
    const gchar *begin = str.c_str();
    const gchar *end = begin + str.bytes();
    return parse_int(begin,end);
  }

  gint32 parse_int(const gchar *str)
  {
    const gchar *end = str;
    while(*end)
      end++;
    return parse_int(str,end);
  }

  gint32 parse_int(const gchar *begin,const gchar *end)
  {
    gint32 ret;
    TryParseIntRes res = try_parse_int(begin,end,ret);
    switch(res)
    {
    case TryParseIntRes_ParseError:
      throw ParseIntException(Glib::ustring(begin,end - begin));

    case TryParseIntRes_OutOfRange:
      throw ParseIntException(Glib::ustring(begin,end - begin));
    }

    return ret;
  }

  ParseIntException::ParseIntException(const Glib::ustring &number_str)
  {
    this->number_str = number_str;
  }

  ParseIntException::~ParseIntException() throw()
  {
  }

  Glib::ustring ParseIntException::get_message() const
  {
    return "\"" + number_str + "\" doesn't represent an integer number.";
  }

  std::vector<Glib::ustring> shell_parse_windows_style(const Glib::ustring &cmd_line)
  {
    std::vector<Glib::ustring> ret;

    const char *c = cmd_line.c_str();

    // find the first non space char
    while(*c == ' ' || *c == '\t')
      c++;

    if(*c)
    {
      Glib::ustring *cur;
      ret.push_back("");
      cur = &ret.back();

      bool in_quotes = false;
      int backslash_count = 0;
      while(*c)
      {
        if(backslash_count && *c != '\\' && *c != '\"')
        {
          cur->append(backslash_count,'\\');
          backslash_count = 0;
        }

        if((*c == '\t' || *c == ' ') && !in_quotes)
        {
          // skip any following spaces
          while(*c == ' ' || *c == '\t')
            c++;

          if(!*c)
            break;

          // begin a new arg
          ret.push_back("");
          cur = &ret.back();
        }
        else if(*c == '\\')
        {
          backslash_count++;
          c++;
        }
        else if(*c == '\"')
        {
          if ((backslash_count & 1)==0)
          {
            // even number of slashes, so half that number of slashes,
            // and the quote is not escaped.
            cur->append(backslash_count / 2,'\\');
            in_quotes = !in_quotes;
          }
          else
          {
            // even number of slashes, so half that number of slashes,
            // and the quote is escaped.
            cur->append(backslash_count / 2,'\\');
            cur->append(1,'\"');
          }
          backslash_count = 0;
          c++;
        }
        else
        {
          cur->append(1,*c);
          c++;
        }
      }
    }

    return ret;
  }

  std::wstring to_wstring(const Glib::ustring &in)
  {
    GError *error;
    gunichar2 *str = g_utf8_to_utf16(in.c_str(),-1,NULL,NULL,&error);
    if(!str)
      throw Glib::Error(error);

    std::wstring ret((wchar_t*)str);

    g_free(str);

    return ret;
  }

  Glib::ustring to_ustring(const wchar_t *src)
  {
    Glib::ustring ret;
    for(const wchar_t *c = src;*c;c++)
      ret += (gunichar)*c;
    return ret;
  }

  void copy_file(const Glib::ustring src_path,const Glib::ustring &dest_path)
  {
    // TODO: this stuff can probably be done using gio.

    FILE *in_file = 0;
    FILE *out_file = 0;

    try
    {
      in_file = fopen(src_path.c_str(),"rb");
      if(!in_file)
      {
        throw Exception("Couldn't open source file \"" + src_path +
          "\" while copying to \"" + dest_path + "\".");
      }

      out_file = fopen(dest_path.c_str(),"wb");
      if(!out_file)
      {
        throw Exception("Couldn't open destination file \"" + dest_path +
          "\" while copying from \"" + src_path + "\".");
      }

      char buffer[1024];
      while(feof(in_file))
      {
        size_t num_read = fread(buffer,1024,1,in_file);
        int error_code = ferror(in_file);
        if(error_code)
        {
          throw Exception("Couldn't read from source file \"" + src_path +
            "\" while copying to \"" + dest_path + "\".");
        }

        size_t num_written = fwrite(buffer,num_read,1,out_file);
        if(num_written != num_read || ferror(out_file))
        {
          throw Exception("Couldn't write to destination file \"" + dest_path +
            "\" while copying from \"" + src_path + "\".");
        }
      }

      fclose(in_file);
      fclose(out_file);
    }
    catch(...)
    {
      if(in_file)
        fclose(in_file);
      if(out_file)
        fclose(out_file);
      throw;
    }
  }

  bool compare_string_begin(const gchar *a_begin,const gchar *a_end,const gchar *b)
  {
    const gchar *a;

    a = a_begin;
    while(a != a_end && *b)
    {
      if(*a != *b)
        return false;

      a++;
      b++;
    }

    // B should be at it's last character.
    return *b == '\0';
  }

  bool compare_string_segment(const gchar *a_begin,const gchar *a_end,
    const gchar *b_begin,const gchar *b_end)
  {
    const gchar *a = a_begin;
    const gchar *b = b_begin;

    while(a != a_end && b != b_end)
    {
      if(*a != *b)
        return false;

      a++;
      b++;
    }

    // they should both be at their last character.
    return a == a_end && b == b_end;
  }

  void trim(Glib::ustring &str)
  {
    Glib::ustring::size_type first_non_ws = str.find_first_not_of(" \t\n\r");
    Glib::ustring::size_type last_non_ws = str.find_last_not_of(" \t\n\r");

    if(first_non_ws == Glib::ustring::npos)
    {
      assert(last_non_ws == Glib::ustring::npos);
      str.clear();
      return;
    }
    else
    {
      assert(last_non_ws != Glib::ustring::npos);
    }

    str.erase(0,first_non_ws);
    last_non_ws -= first_non_ws;

    Glib::ustring::size_type num_to_erase = str.bytes() - last_non_ws - 1;
    if(num_to_erase)
      str.erase(last_non_ws + 1,num_to_erase);
  }

  Glib::ustring get_trimmed(const Glib::ustring &in)
  {
    Glib::ustring ret = in;
    trim(ret);
    return ret;
  }

  int get_next_pow2_log(guint32 v)
  {
    guint32 left = v;

    int ret = 0;
    if(left & 0xffff0000)
    {
      ret += 16;
      left >>= 16;
    }
    if(left & 0xff00ff00)
    {
      ret += 8;
      left >>= 8;
    }
    if(left & 0xf0f0f0f0)
    {
      ret += 4;
      left >>= 4;
    }
    if(left & 0xcccccccc)
    {
      ret += 2;
      left >>= 2;
    }
    if(left & 0xaaaaaaaa)
    {
      ret += 1;
      left >>= 1;
    }

    if((guint32)1 << ret != v)
      ret++;

    return ret;
  }

  int get_next_pow2_log_size_t(size_t v)
  {
    assert(sizeof(size_t) == 4);
    return get_next_pow2_log((guint32)v);
  }

  guint32 get_next_pow2(guint32 v)
  {
    int exp = get_next_pow2_log(v);
    return 1 << exp;
  }

  size_t get_next_pow2_size_t(size_t v)
  {
    assert(sizeof(size_t) == 4);

    int exp = get_next_pow2_log_size_t(v);
    return (size_t)1 << exp;
  }

  ///////////
  // StringSegment
  void StringSegment::trim()
  {
    while(begin != end && isspace(*begin))
      begin++;
    while(begin != end && isspace(*(end - 1)))
      end--;
  }
}
